<template>
    <div class="x6-container">
        <div id="x6Box" class="x6Box"></div>
    </div>
</template>
<script>
import { Graph, Addon } from '@antv/x6';
import '@antv/x6-vue-shape';
import dagre from 'dagre';
import register from './x6.js';
export default {
    props: {
        hierarchy: {
            type: Boolean,
            default: false,
        },
        nodeMove: {
            type: Boolean,
            default: false,
        },
        layout: {
            type: String,
            default: '',
            options: 'LR RL TB BT',
        },
        valConnection: {
            type: Function,
            default: edge => {
                return {
                    bool: true,
                    message: '',
                };
            },
            description: '用于验证连接线有效性，也可以对连接线做出一些修改',
        },
        initData: {
            type: Array,
            default: () => [],
        },
        gridOption: {
            type: Object,
            default: () => ({
                visible: true,
            }),
            description: '网格配置对象，详细配置请参考官方文档',
        },
        edgeRouter: {
            type: Object,
            default: () => ({
                // name: 'er',
                // args: {
                //     offset: 40,
                // },
            }),
            description: '路由方式配置参数，详细请查阅官方文档',
        },
    },
    data() {
        return {
            graph: null, //画布实例
            dnd: null, //拖拽实例
        };
    },
    mounted() {
        // 注册vue节点及修改边线配置等
        register(this);
        this.InitX6();
    },
    beforeDestroy() {
        this.graph.dispose();
    },
    methods: {
        // 初始化X6
        InitX6() {
            let _this = this;
            // graph实例化
            this.graph = new Graph({
                container: document.getElementById('x6Box'),
                grid: this.gridOption,
                panning: {
                    enabled: true,
                    // modifiers: 'ctrl',
                },
                mousewheel: {
                    enabled: true,
                    modifiers: ['ctrl'],
                },
                interacting: {
                    nodeMovable: this.nodeMove, //禁止节点移动控制变量
                },
                connecting: {
                    router: 'orth',
                    allowLoop: false,
                    allowBlank: false,
                    allowNode: false,
                    allowEdge: false,
                    snap: {
                        radius: 60,
                    },
                    validateEdge({ edge, type, previous }) {
                        let obj = _this.valConnection({ edge });
                        if (!obj.bool) {
                            this.$YsMessage.error(obj.message);
                        }

                        if (_this.layout) {
                            let source = edge.source.port.split('_');
                            let target = edge.target.port.split('_');
                            if (source[0] == target[0] && (_this.layout == 'LR' || _this.layout == 'TB') && !(source[1] % 2) && target[1] % 2) {
                                return true;
                            } else if (
                                source[0] == target[0] &&
                                (_this.layout == 'RL' || _this.layout == 'BT') &&
                                !(target[1] % 2) &&
                                source[1] % 2
                            ) {
                                return true;
                            } else {
                                let obj = {
                                    LR: '右出左进',
                                    RL: '左出右进',
                                    TB: '下出上进',
                                    BT: '上出下进',
                                };
                                _this.$YsMessage.info(`请遵循节点连接'${obj[_this.layout]}'原则!`);
                                return false;
                            }
                        }
                        return obj.bool;
                    },
                },
                snapline: true,
                onToolItemCreated({ name, cell, tool }) {
                    if (name === 'vertices') {
                        const options = tool.options;
                        if (options && options.index % 2 === 1) {
                            tool.setAttrs({ fill: 'red' });
                        }
                    }
                },
            });

            this.dnd = new Addon.Dnd({
                target: this.graph,
                scaled: false,
                animation: true,
            });
            // 控制连接桩显示/隐藏
            const showPorts = (ports, show) => {
                for (let i = 0, len = ports.length; i < len; i = i + 1) {
                    ports[i].style.visibility = show ? 'visible' : 'hidden';
                }
            };
            this.graph.on('node:mouseenter', () => {
                const container = document.getElementById('x6Box');
                const ports = container.querySelectorAll('.x6-port-body');
                showPorts(ports, true);
            });
            this.graph.on('node:mouseleave', () => {
                const container = document.getElementById('x6Box');
                const ports = container.querySelectorAll('.x6-port-body');
                showPorts(ports, false);
            });
            // //节点连线 鼠标mouseenter事件 删除连线
            this.graph.on('edge:mouseenter', ({ edge }) => {
                edge.addTools('vertices');
                edge.addTools([
                    {
                        name: 'button-remove',
                        args: {
                            distance: -30,
                        },
                    },
                ]);
            });
            //节点连线 鼠标mouseleave事件 删除连线
            this.graph.on('edge:mouseleave', ({ edge }) => {
                edge.removeTool('vertices');
                edge.removeTool('button-remove');
            });
            // 边链接事件
            this.graph.on('edge:connected', ({ isNew, edge }) => {
                if (isNew) {
                    const source = edge.getSourceNode();
                    const target = edge.getTargetNode();
                    // doSomething...
                    edge.attrs = {
                        line1: {
                            stroke: {
                                type: 'linearGradient',
                                stops: [
                                    { offset: '0%', color: '#E77930' },
                                    { offset: '50%', color: '#73d13d' },
                                    { offset: '100%', color: '#00CEFF' },
                                ],
                            },
                        },
                    };

                    if (this.layout) {
                        this.InsertToParent(source.id, [target.id]);
                    }

                    let x6Arr = this.graph.toJSON().cells;
                    this.UpdateX6(x6Arr);
                }
            });
            //  删除边
            this.graph.on('edge:removed', ({ edge }) => {
                // doSomething...
                const source = edge.source;
                const target = edge.target;
                this.DeleteFromParent(source.cell, [target.cell]);
                let x6Arr = this.graph.toJSON().cells;
                this.UpdateX6(x6Arr);
            });
            window.setTimeout(() => {
                this.UpdateX6(this.initData);
                this.graph.zoomToFit({ padding: 10, maxScale: 1 });
            }, 1000);
        },
        // 删除节点
        RemoveNode(cell) {
            this.graph.removeNode(cell);
        },
        // 更新拓扑视图
        UpdateX6(x6Arr) {
            let cells = [];
            x6Arr.forEach(item => {
                if (item.shape == 'edge') {
                    let edge = this.graph.createEdge(item);
                    cells.push(edge);
                } else {
                    if (item.shape == 'card') {
                        // 找到vue节点删除component属性
                        delete item.component;
                        item.data.render = this.EvalStr(item.data.render);
                    }
                    cells.push(this.graph.createNode(item));
                }
            });
            this.graph.resetCells(cells);
        },
        CenterContent() {
            this.graph.centerContent();
        },
        // 创建边
        CreateEdge(source, target, obj) {
            let edge = {
                ...obj,
            };
            return edge;
        },
        // 获取拓扑反序列化Json
        GetJson() {
            let json = this.graph.toJSON();
            json.cells.forEach(cell => {
                if (cell.shape == 'card') {
                    cell.data.render = cell.data.render.toString();
                }
            });
            return json;
        },
        // 自动布局
        Layout() {
            if (!this.layout) return;
            // 布局方向
            const dir = this.layout; // LR RL TB BT
            const nodes = this.graph.getNodes();
            const edges = this.graph.getEdges();
            const g = new dagre.graphlib.Graph();
            g.setGraph({ rankdir: dir, nodesep: 50, ranksep: 80 });
            g.setDefaultEdgeLabel(() => ({}));

            const width = 200;
            const height = 150;
            nodes.forEach(node => {
                g.setNode(node.id, { width, height });
            });

            edges.forEach(edge => {
                const source = edge.getSource();
                const target = edge.getTarget();
                g.setEdge(source.cell, target.cell);
            });

            dagre.layout(g);

            this.graph.freeze();

            g.nodes().forEach(id => {
                const node = this.graph.getCell(id);
                if (node) {
                    const pos = g.node(id);
                    node.position(pos.x, pos.y);
                }
            });

            edges.forEach(edge => {
                const source = edge.getSourceNode();
                const target = edge.getTargetNode();
                const sourceBBox = source.getBBox();
                const targetBBox = target.getBBox();
                if ((dir === 'LR' || dir === 'RL') && sourceBBox.y !== targetBBox.y) {
                    const gap = dir === 'LR' ? targetBBox.x - sourceBBox.x - sourceBBox.width : -sourceBBox.x + targetBBox.x + targetBBox.width;
                    const fix = dir === 'LR' ? sourceBBox.width : 0;
                    const x = sourceBBox.x + fix + gap / 2;
                    edge.setVertices([
                        { x, y: sourceBBox.center.y },
                        { x, y: targetBBox.center.y },
                    ]);
                } else if ((dir === 'TB' || dir === 'BT') && sourceBBox.x !== targetBBox.x) {
                    const gap = dir === 'TB' ? targetBBox.y - sourceBBox.y - sourceBBox.height : -sourceBBox.y + targetBBox.y + targetBBox.height;
                    const fix = dir === 'TB' ? sourceBBox.height : 0;
                    const y = sourceBBox.y + fix + gap / 2;
                    edge.setVertices([
                        { x: sourceBBox.center.x, y },
                        { x: targetBBox.center.x, y },
                    ]);
                } else {
                    edge.setVertices([]);
                }
            });

            this.graph.unfreeze();
        },
        // 创建父子关系
        InsertToParent(parentId, ChildIds) {
            let x6Arr = this.graph.toJSON().cells;
            if (this.hierarchy) {
                x6Arr.some(item => {
                    if (parentId == item.id) {
                        item.children = item.children || [];
                        item.children.push(...ChildIds);
                        return true;
                    }
                });
            }
            this.UpdateX6(x6Arr);
        },
        // 决裂父子关系
        DeleteFromParent(parentId, ChildIds) {
            let x6Arr = this.graph.toJSON().cells;
            if (this.hierarchy) {
                x6Arr.some(item => {
                    if (parentId == item.id) {
                        item.children = (item.children || []).reduce((pre, cur) => {
                            !ChildIds.some(item => item == cur) && pre.push(cur);
                            return pre;
                        }, []);
                        return true;
                    }
                });
            }

            this.UpdateX6(x6Arr);
        },
        // 创建ID枚举
        CreateNodeDic(arr) {
            let obj = {};
            arr.forEach(item => {
                obj[item.id] = item;
            });
            return obj;
        },
        // 修改节点数据，使用此方法修改可以触发双向绑定
        SetData(node, key, value) {
            let xData = { ...node.data };
            xData[key] = value;
            node.setData({ ...xData });
        },
        // 拖拽创建节点
        DndStart(
            e,
            item,
            nodeInfo = {
                width: 180,
                height: 136,
                shape: 'card',
                data: {
                    title: '申请节点',
                    body: `添加流程${item}`,
                    nodeType: '0',
                    render: (h, param) => h('div', {}, [h('span', param.title), h('div', param.body), h('YsInput')]),
                },
            }
        ) {
            //然后将节点的 shape 属性指定为注册的节点名称
            const node = this.graph.createNode({
                ...nodeInfo,
            });
            this.dnd.start(node, e);
        },
        EvalStr(str) {
            let renderFun = str;
            if (typeof str == 'string') {
                renderFun = eval(str);
            }
            return renderFun;
        },
        /**
         * param key 事件名称
         * param detail 返回信息
         */
        TouchEvent(key, detail) {
            window.dispatchEvent(
                new CustomEvent(key, {
                    detail,
                })
            );
        },
    },
};
</script>
<style lang="less">
.x6-container {
    width: 100%;
    height: 100%;
    overflow: hidden;
    .x6Box {
        width: 100%;
        height: 100%;
    }
}
</style>
